//Package mycobra 实现简化版 cobra 库, 支持带子命令的命令行程序开发
package mycobra

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

	flag "github.com/spf13/pflag"
)

// Command 结构定义了应用程序所使用的命令.
// 使用者应该在命令定义中自定义用法和描述, 以确保可用性.
type Command struct {

	// Use 描述使用方法的单行信息.
	// 推荐语法如下:
	//   [ ] 标识一个可选参数，未在 [ ] 中的参数是必需的.
	//   ... 表示可以为前一个参数指定多个值.
	//   |   表示互斥，可以使用 | 左侧或右侧的参数, 但不能在一条命令中同时使用两个参数.
	//   { } 分隔一组互斥的参数.
	// Example: add [-F file | -D dir]... [-f format] profile
	Use string

	// Short 在 'help' 中输出的短描述.
	Short string

	// Long 在 'help <this-command>' 中输出的长描述.
	Long string

	// Run 运行命令, 大多命令只需要实现这个函数.
	Run func(cmd *Command, args []string)

	// RunE 运行命令并返回错误.
	RunE func(cmd *Command, args []string) error

	// helpFunc 用户自定义 help 函数.
	helpFunc func(*Command, []string)

	// helpCommand help 命令. 如果用户未定义, mycobra 会使用默认 help 命令.
	helpCommand *Command

	// args 通过 flags 解析得到的参数.
	args []string

	// parent 该命令的父命令.
	parent *Command

	// commands 该命令支持的子命令列表.
	commands []*Command
}

// Execute 执行该命令.
func (cmd *Command) Execute() error {
	cmd.InitDefaultHelpCmd()
	args := cmd.args
	// Workaround FAIL with "go test -v" or "cobra.test -test.v", see #155
	if cmd.args == nil && filepath.Base(os.Args[0]) != "cobra.test" {
		args = os.Args[1:]
	}
	targetCmd, flags, err := cmd.Find(args)
	if err != nil {
		return err
	}
	err = targetCmd.execute(flags)
	if err != nil {
		// Always show help if requested, even if SilenceErrors is in effect
		if err == flag.ErrHelp {
			targetCmd.HelpFunc()(targetCmd, args)
			return nil
		}
	}
	return err
}

// execute 执行命令.
func (cmd *Command) execute(a []string) (err error) {
	if cmd == nil {
		return fmt.Errorf("Called Execute() on a nil Command")
	}
	if !cmd.Runnable() {
		return flag.ErrHelp
	}
	if cmd.RunE != nil {
		err := cmd.RunE(cmd, a)
		return err
	}
	cmd.Run(cmd, a)
	return nil
}

// Runnable 确定命令本身是否可运行.
func (cmd *Command) Runnable() bool {
	return cmd.Run != nil || cmd.RunE != nil
}

// Find 根据输入参数寻找要执行的子命令, 剩余参数作为 flags 返回.
func (cmd *Command) Find(args []string) (*Command, []string, error) {
	var innerfind func(*Command, []string) (*Command, []string)
	innerfind = func(cmd *Command, innerArgs []string) (*Command, []string) {
		// args without flags
		argsWOflags := innerArgs
		if len(argsWOflags) == 0 {
			return cmd, innerArgs
		}
		nextSubCmd := argsWOflags[0]
		targetCmd := cmd.findNext(nextSubCmd)
		if targetCmd != nil {
			return innerfind(targetCmd, argsMinusFirstX(innerArgs, nextSubCmd))
		}
		return cmd, innerArgs
	}
	commandFound, flags := innerfind(cmd, args)
	return commandFound, flags, nil
}

// findNext 在该命令的子命令列表中找到要执行的下一个命令.
func (cmd *Command) findNext(next string) *Command {
	for _, cmd := range cmd.commands {
		if cmd.Name() == next {
			return cmd
		}
	}
	return nil
}

// argsMinusFirstX 从参数中去掉已找到的子命令.
func argsMinusFirstX(args []string, x string) []string {
	for i, y := range args {
		if x == y {
			ret := []string{}
			ret = append(ret, args[:i]...)
			ret = append(ret, args[i+1:]...)
			return ret
		}
	}
	return args
}

// AddCommand 为该父命令增加子命令.
func (cmd *Command) AddCommand(subCmds ...*Command) {
	for i, subCmd := range subCmds {
		if subCmds[i] == cmd {
			panic("Command can't be a child of itself")
		}
		subCmds[i].parent = cmd
		cmd.commands = append(cmd.commands, subCmd)
	}
}

// RemoveCommand 从该父命令中删除子命令.
func (cmd *Command) RemoveCommand(rmCmds ...*Command) {
	commands := []*Command{}
main:
	for _, command := range cmd.commands {
		for _, rmCmd := range rmCmds {
			if command == rmCmd {
				command.parent = nil
				continue main
			}
		}
		commands = append(commands, command)
	}
	cmd.commands = commands
}

// Name 返回 Command.Use 字段首单词作为该命令的名字.
func (cmd *Command) Name() string {
	name := cmd.Use
	i := strings.Index(name, " ")
	if i >= 0 {
		name = name[:i]
	}
	return name
}

// Root 返回 root 根命令.
func (cmd *Command) Root() *Command {
	if cmd.parent != nil {
		return cmd.parent.Root()
	}
	return cmd
}
